package TDADiccionario;

import TDALista.InvalidPositionException;
import TDALista.ListaSE;
import TDALista.Position;
import TDALista.PositionList;
import TDAMapeo.Entrada;
import TDAMapeo.Entry;
import TDAMapeo.InvalidEntryException;
import TDAMapeo.InvalidKeyException;


/**
 * Diccionario implementado con un hash abierto
 * @author Jordan
 *
 * @param <K>
 * @param <V>
 */
public class Diccionario<K,V> implements Dictionary<K, V> {

	private int size;
	private ListaSE<Entrada<K, V>>[] contenedor;
	private float factorDeCarga; // size/Cant de listas(buckets)
	
	
	/**
	 * constructor, crea un diccionario vacio
	 */
	public Diccionario(){
		size=0;
		factorDeCarga=0;
		contenedor=(ListaSE<Entrada<K, V>>[])(new ListaSE[7]);
		for(int i=0; i<contenedor.length; i++)
			contenedor[i]=new ListaSE<Entrada<K,V>>();
			
	}
	
	@Override
	public int size() {
		return size;
	}

	@Override
	public boolean isEmpty() {
		return size==0;
	}

	@Override
	public Entrada<K, V> find(K key) throws InvalidKeyException {
		check(key);
//		Entry<K, V> res;
		for(Entrada<K, V> e: contenedor[hash(key)])
			if(e.getKey().equals(key))
				return e;
		return null;
	}

	/**
	 * hace hash a la clave y devuelve donde debe hubicarse la entrada
	 * @param key clave de la entrada a hacerle el hash
	 * @return posicion donde ubicar la entrada
	 */
	private int hash(K key) {
		return Math.abs(key.hashCode() % contenedor.length);
	}

	/**
	 * verifica que la clave no sea nula o invalida
	 * @param key clave a verificar
	 * @throws InvalidKeyException es lanzada cuando la clave es invalida
	 */
	private void check(K key) throws InvalidKeyException{
		if (key==null)
			throw new InvalidKeyException("clave nula");
		
	}

	@Override
	public Iterable<Entry<K, V>> findAll(K key) throws InvalidKeyException {
		if(key==null)
			throw new InvalidKeyException("la clave ingresada es nula");
		int hashn=hash(key);
		ListaSE<Entry<K, V>> res=new ListaSE<Entry<K,V>>();
		if(contenedor[hashn].size()!=0){
			for(Entrada<K, V> e : contenedor[hashn])
				if(e.getKey().equals(key))
					res.addLast(e);
		}
		return res;
	}

	@Override
	public Entrada<K, V> insert(K key, V value) throws InvalidKeyException {
		// TODO Auto-generated method stub
		check(key);
		int hashn=hash(key);
		Entrada<K, V> entr=new Entrada<K, V>(key, value);
		
		contenedor[hashn].addLast(entr);
		size++;
		factorDeCarga=(float)size/contenedor.length;
		if(factorDeCarga>0.75)//si el factor de carga es elevado
			rehash(hashn);
		return entr;
	}

	private void rehash(int indice) {
		int hashnue;
		ListaSE<Entrada<K, V>>[] contnue=(ListaSE<Entrada<K, V>>[])(new ListaSE[contenedor.length*2]);
		for(int i=0; i<contnue.length; i++)
			contnue[i]=new ListaSE<Entrada<K,V>>();
		
		for(ListaSE<Entrada<K, V>> li: contenedor){
			for(Entrada<K, V> e: li){
				hashnue=Math.abs(e.getKey().hashCode() % contnue.length);
				contnue[hashnue].addLast(e);
			}
		}
		contenedor=contnue;
		
		factorDeCarga=(float)size/contnue.length;
	}

	@Override
	public Entry<K, V> remove(Entry<K, V> entr) throws InvalidEntryException{
		if(entr==null)
			throw new InvalidEntryException("la entrada es nula");
		int hashn=hash(entr.getKey());
		Entry<K, V> res=null;
		for(Position<Entrada<K, V>> e: contenedor[hashn].positions()){
			if(e.element()==entr)
				try {
					res=e.element();
					contenedor[hashn].remove(e);
				} catch (InvalidPositionException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
		}
		size--;
		return res;
	}

	@Override
	public Iterable<Entry<K, V>> entries() {
		PositionList<Entry<K, V>> pl=new ListaSE<Entry<K,V>>();
		for(ListaSE<Entrada<K, V>> li: contenedor)
			for(Entrada<K, V> e: li)
//				if(e!=null)
				pl.addLast(e);
		return pl;
	}

}
